import axios, {AxiosInstance, AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse} from "axios";
import {ElMessageBox, ElNotification} from "element-plus";
import {AxiosCanceler} from "./utils/axiosCancel";
import {ResultData} from "./utils/models";
import {useAuthStore} from "@/store/modules/auth";
import router from "@/router";
import {useMenuStore} from "@/store/modules/menu.ts";
import {useSearchStore} from "@/store/modules/search.ts";
import {useEnvelopeStore} from "@/store/modules/envelope.ts";
import {useSysGlobalStore} from "@/store/system/global.ts";
import {useSysKeepAliveStore} from "@/store/system/keepAlive.ts";
import {useSysViewTagsStore} from "@/store/system/viewTags.ts";
import {encryptPayload, verifySignature} from "@/utils/encrypt.ts";

//可设置公共请求头，会追加到headers中,如有需要
const publicHeaders = {}

const config = {
    baseURL: import.meta.env.VITE_VUE_API_BASEURL as string,
    // 设置超时时间
    timeout: import.meta.env.VITE_TIMEOUT as number,
    // 跨域时候允许携带凭证
    withCredentials: false,
};

export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
    cancel?: boolean;
}

//FIX 多个API同时401时疯狂弹窗BUG
let MessageBox_401_show = false

const axiosCanceler = new AxiosCanceler();

class RequestHttp {
    service: AxiosInstance;

    public constructor(config: AxiosRequestConfig) {
        // instantiation
        this.service = axios.create(config);

        /**
         * @description 请求拦截器
         * 客户端发送请求 -> [请求拦截器] -> 服务器
         * token校验(JWT) : 接受服务器返回的 token,存储到 vuex/pinia/本地储存当中
         */
        this.service.interceptors.request.use(
            (config: CustomAxiosRequestConfig) => {
                const authStore = useAuthStore();
                // 重复请求不需要取消，在 api 服务中通过指定的第三个参数: { cancel: false } 来控制
                config.cancel ??= true;
                config.cancel && axiosCanceler.addPending(config);
                if (authStore.token) {
                    config.headers[import.meta.env.VITE_TOKEN_NAME] = "Bearer " + authStore.token;
                }
                Object.assign(config.headers, publicHeaders);
                const envelopeStore = useEnvelopeStore();
                if (envelopeStore.key && import.meta.env.VITE_NODE_ENV === 'production') {
                    if (config.data instanceof FormData) {
                        const formData = new FormData();
                        const normalFields: Record<string, any> = {};
                        (config.data as FormData).forEach((value, key) => {
                            if (value instanceof File) {
                                formData.append(key, value);
                            } else {
                                normalFields[key] = value;
                            }
                        });
                        if (Object.keys(normalFields).length > 0) {
                            const encrypted = encryptPayload(normalFields, envelopeStore.key);
                            formData.append('encrypted', encrypted.encrypted);
                            formData.append('nonce', encrypted.nonce);
                            formData.append('data', encrypted.data);
                        }
                        config.data = formData;
                        delete config.headers['Content-Type'];
                    } else {
                        console.log(config.data);
                        const encrypted = encryptPayload(config.data ?? {}, envelopeStore.key)
                        config.data = JSON.stringify(encrypted)
                        config.headers['Content-Type'] = 'application/json'
                    }
                }
                return config;
            },
            (error: AxiosError) => {
                return Promise.reject(error);
            }
        );

        /**
         * @description 响应拦截器
         *  服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
         */
        this.service.interceptors.response.use(
            (response: AxiosResponse & { config: CustomAxiosRequestConfig }) => {
                const {data, config} = response;
                axiosCanceler.removePending(config);
                const envelopeStore = useEnvelopeStore();
                if (envelopeStore.key && import.meta.env.VITE_NODE_ENV === 'production') {
                    const isValid = verifySignature(data, envelopeStore.key);
                    if (!isValid) {
                        ElNotification.error({
                            title: 'Signature failed',
                            message: '验证签名失败。'
                        });
                        return Promise.reject(data);
                    }
                }
                switch (data.code) {
                    case 200:
                        return data;
                    case 401:
                        if (!MessageBox_401_show) {
                            MessageBox_401_show = true;
                            ElMessageBox.confirm(data.message, '提示', {
                                type: 'warning',
                                confirmButtonClass: 'el-button--primary',
                                showCancelButton: false,
                            }).then(() => {
                                useAuthStore().$reset();
                                useMenuStore().$reset();
                                useSearchStore().$reset();
                                useEnvelopeStore().$reset();
                                useSysGlobalStore().$reset();
                                useSysKeepAliveStore().$reset();
                                useSysViewTagsStore().$reset();
                                router.replace({path: '/login'}).then(() => {
                                });
                            }).finally(() => {
                                MessageBox_401_show = false;
                            });
                        }
                        return Promise.reject(data);
                    default:
                        ElNotification.error({
                            title: "Error Code:" + data.code,
                            message: data.message || `status:${data.code}，未知错误！`
                        });
                        return Promise.reject(data);
                }
            },
            async (error: AxiosError) => {
                const {response} = error;
                if (error.message.indexOf("timeout") !== -1) {
                    ElNotification.error({
                        title: "Timeout Error",
                        message: "请求超时！请您稍后重试",
                    });
                }
                if (error.message.indexOf("Network Error") !== -1) {
                    ElNotification.error({
                        title: "Network Error",
                        message: "网络错误！请您稍后重试",
                    });
                }
                if (response) {
                    if (response.status === 422) {
                        const data = response.data as { message: string };
                        ElNotification.error({
                            title: "Error Code" + response.status,
                            message: data.message,
                        });
                    } else {
                        ElNotification.error({
                            title: "Error Message",
                            message: "服务器内部错误!",
                        });
                    }
                }
                if (!window.navigator.onLine) await router.replace("/500");
                return Promise.reject(error);
            }
        );
    }

    /**
     * @description 常用请求方法封装
     */
    get<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
        return this.service.get(url, {params, ..._object});
    }

    post<T>(url: string, params?: object | string, _object = {}): Promise<ResultData<T>> {
        return this.service.post(url, params, _object);
    }

    put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
        return this.service.put(url, params, _object);
    }

    delete<T>(url: string, params?: any, _object = {}): Promise<ResultData<T>> {
        return this.service.delete(url, {params, ..._object});
    }

    download(url: string, params?: object, _object = {}): Promise<BlobPart> {
        return this.service.post(url, params, {..._object, responseType: "blob"});
    }
}

export default new RequestHttp(config);
